# include "AI.h"

# include "Game.h"
# include "Renderer.h"
# include "GUI.h"

# include "World.h"
# include "Map.h"
# include "ObjectAction.h"

# include "Personage.h"
# include "Hero.h"
# include "Enemy.h"
# include "PNJ.h"

AI::AI( Game* game) : Module( game)
{

}

AI::~AI()
{

}

void AI::setWindow( Ogre::RenderWindow* window)
{
	myWindow = window;
}

void AI::setWorld( World* world)
{
	myWorld = world;

	myHero = world->getHero();
	myMap = world->getMap();
}

void AI::init( Renderer* renderer, GUI* gui)
{
	myRenderer = renderer;
	myGUI = gui;

	myEnemyHighlight = NULL;

	myRaySceneQuery = myRenderer->getSceneManager()->createRayQuery( Ogre::Ray());
	myRaySceneQuery->setSortByDistance( true);
	myRaySceneQuery->setQueryTypeMask( Ogre::SceneManager::ENTITY_TYPE_MASK);
}

void AI::loadWorld( const std::string& name)
{
	lua_State* stateTmp = lua_open();
	luaL_openlibs( stateTmp);
	luaL_dofile( stateTmp, LUA_NAV_MESH);
	lua_settop( stateTmp, 0);
	int indiceTmp = 1;

	lua_getglobal( stateTmp, name.c_str());

	lua_pushnumber( stateTmp, indiceTmp++);
	lua_gettable( stateTmp, -2);
	unsigned int number = lua_tonumber( stateTmp, -1);
	lua_pop( stateTmp, 1);

	for ( unsigned int i = 0; i < number; i++ )
	{
		myNavMesh.push_back( new NavMeshPol());
		myNavMesh[i]->number = i;

		lua_pushnumber( stateTmp, indiceTmp++);
		lua_gettable( stateTmp, -2);
		myNavMesh[i]->middle.x = lua_tonumber( stateTmp, -1);
		lua_pop( stateTmp, 1);

		lua_pushnumber( stateTmp, indiceTmp++);
		lua_gettable( stateTmp, -2);
		myNavMesh[i]->middle.y = lua_tonumber( stateTmp, -1);
		lua_pop( stateTmp, 1);

		lua_pushnumber( stateTmp, indiceTmp++);
		lua_gettable( stateTmp, -2);
		unsigned int corner = lua_tonumber( stateTmp, -1);
		lua_pop( stateTmp, 1);

		for ( unsigned int j = 0; j < corner; j++ )
		{
			myNavMesh[i]->points.push_back( Ogre::Vector2());
			
			lua_pushnumber( stateTmp, indiceTmp++);
			lua_gettable( stateTmp, -2);
			myNavMesh[i]->points[j].x = lua_tonumber( stateTmp, -1);
			lua_pop( stateTmp, 1);

			lua_pushnumber( stateTmp, indiceTmp++);
			lua_gettable( stateTmp, -2);
			myNavMesh[i]->points[j].y = lua_tonumber( stateTmp, -1);
			lua_pop( stateTmp, 1);
		}

		lua_pushnumber( stateTmp, indiceTmp++);
		lua_gettable( stateTmp, -2);
		unsigned int neighbor = lua_tonumber( stateTmp, -1);
		lua_pop( stateTmp, 1);

		for ( unsigned int j = 0; j < neighbor; j++ )
		{
			myNavMesh[i]->neighborMidPoint.push_back( Ogre::Vector2());
			
			lua_pushnumber( stateTmp, indiceTmp++);
			lua_gettable( stateTmp, -2);
			myNavMesh[i]->neighborMidPoint[j].x = lua_tonumber( stateTmp, -1);
			lua_pop( stateTmp, 1);

			lua_pushnumber( stateTmp, indiceTmp++);
			lua_gettable( stateTmp, -2);
			myNavMesh[i]->neighborMidPoint[j].y = lua_tonumber( stateTmp, -1);
			lua_pop( stateTmp, 1);
		}
	}

	lua_close( stateTmp);

	for ( unsigned int i = 0; i < myNavMesh.size(); i++ )
	{
		for ( unsigned int j = 0; j < myNavMesh[i]->neighborMidPoint.size(); j++ )
		{
			for ( unsigned int k = 0; k < myNavMesh.size(); k++)
			{
				for ( unsigned int l = 0; l < myNavMesh[k]->neighborMidPoint.size(); l++ )
				{
					if ( i != k && myNavMesh[i]->neighborMidPoint[j] == myNavMesh[k]->neighborMidPoint[l] )
					{
						myNavMesh[i]->neighbors.push_back( myNavMesh[k]);
						myNavMesh[i]->distances.push_back( (myNavMesh[k]->middle.x - myNavMesh[i]->middle.x)*(myNavMesh[k]->middle.x - myNavMesh[i]->middle.x) + (myNavMesh[k]->middle.y - myNavMesh[i]->middle.y)*(myNavMesh[k]->middle.y - myNavMesh[i]->middle.y));
					}
				}
			}
		}
	}
}

void AI::unloadWorld()
{
	while ( myNavMesh.size() )
	{
		delete myNavMesh.back();
		myNavMesh.pop_back();
	}
}

void AI::oneFrame()
{
	handleEvents();
}

void AI::findDirection( Enemy* enemy)
{
	myHero->clearPath();
	pathFinding( myHero, enemy->getPosition());
	myHero->setAction( GO_HIT);
}

void AI::findDirection( Hero* hero, Enemy* enemy)
{
	Ogre::Vector3 directionTmp = hero->getPosition() - enemy->getPosition();
	Ogre::Vector3 directionNormalTmp = directionTmp.normalisedCopy();

	directionTmp -= directionNormalTmp * ( hero->getSize() + enemy->getSize());
	enemy->setDirection( directionTmp);
	enemy->setAction( GO_HIT);
}

void AI::heroDistance( Enemy* enemy)
{
	Ogre::Vector3 directionTmp = myHero->getPosition() - enemy->getPosition();
	Ogre::Real distanceTmp = directionTmp.length();

	if ( distanceTmp <= enemy->getDetectionDistance() )
	{
		Ogre::Vector3 directionNormalTmp = directionTmp.normalisedCopy();
		
		directionTmp -= directionNormalTmp * (myHero->getSize() + enemy->getSize());
		enemy->setDirection( directionTmp);
		enemy->setAction( GO_HIT);
		enemy->setTarget( myHero);
	}
}

void AI::handleEvents()
{
	while ( !myEvents.empty() )
	{
		Event evtTmp = myEvents.front();
		myEvents.pop();

		if ( evtTmp.message == MOUSE_LEFT_CLICKED )
		{
			mouseClicked( evtTmp.x, evtTmp.y);
		}

		if ( evtTmp.message == REQUEST )
		{
			mouseRequest( evtTmp.x, evtTmp.y);
		}
	}
}

void AI::mouseClicked( float x, float y)
{
	Ogre::Real offsetXTmp = x / myWindow->getWidth();
	Ogre::Real offsetYTmp = y / myWindow->getHeight();

	Ogre::Ray testRayTmp = myHero->getCamera()->getCameraToViewportRay( offsetXTmp, offsetYTmp);
	myRaySceneQuery->setRay( testRayTmp);
	myRaySceneQuery->setQueryMask( ENEMY_FLAG | PNJ_FLAG | WALL_FLAG | ACTION_FLAG);

	Ogre::RaySceneQueryResult& resultTmp = myRaySceneQuery->execute();
	
	if ( resultTmp.begin() != resultTmp.end() )
	{
		if ( resultTmp.begin()->movable )
		{	
			if ( resultTmp.begin()->movable->getQueryFlags() & ENEMY_FLAG )
			{
				Enemy* enemyTmp = myWorld->findEnemy( resultTmp.begin()->movable->getParentSceneNode()->getParentSceneNode()->getName());

				Ogre::Vector3 directionTmp = enemyTmp->getPosition() - myHero->getPosition();
				Ogre::Vector3 directionNormalTmp = directionTmp.normalisedCopy();
				Ogre::Real distanceTmp = directionTmp.length();

				if ( distanceTmp <= (myHero->getSize() + enemyTmp->getSize()) )
				{
					myHero->setAction( HIT);
					myHero->setTarget( enemyTmp);
				}

				else
				{
					myHero->clearPath();
					pathFinding( myHero, enemyTmp->getPosition());
					myHero->setAction( GO_HIT);
					myHero->setTarget( enemyTmp);
				}
			}

			else if ( resultTmp.begin()->movable->getQueryFlags() & PNJ_FLAG )
			{
				PNJ* pnjTmp = myWorld->findPNJ( resultTmp.begin()->movable->getParentSceneNode()->getParentSceneNode()->getName());

				Ogre::Vector3 directionTmp = pnjTmp->getPosition() - myHero->getPosition();
				Ogre::Vector3 directionNormalTmp = directionTmp.normalisedCopy();
				Ogre::Real distanceTmp = directionTmp.length();

				if ( distanceTmp <= (myHero->getSize() + pnjTmp->getSize()) )
				{
					myHero->setAction( TALK);
					myHero->setTalk( pnjTmp);
				}

				else
				{
					myHero->clearPath();
					pathFinding( myHero, pnjTmp->getPosition());
					myHero->setAction( GO_TALK);
					myHero->setTalk( pnjTmp);
				}
			}

			else if ( resultTmp.begin()->movable->getQueryFlags() & WALL_FLAG )
			{
				return;
			}

			if ( resultTmp.begin()->movable->getQueryFlags() & ACTION_FLAG )
			{
				ObjectAction* actionTmp = myMap->findAction( resultTmp.begin()->movable->getParentSceneNode()->getName());

				Ogre::Vector3 directionTmp = actionTmp->getPosition() - myHero->getPosition();
				Ogre::Vector3 directionNormalTmp = directionTmp.normalisedCopy();
				Ogre::Real distanceTmp = directionTmp.length();

				if ( distanceTmp <= myHero->getSize() )
				{
					myHero->setAction( ACTION);
					myHero->setActionTarget( actionTmp);
				}

				else
				{
					myHero->clearPath();
					pathFinding( myHero, actionTmp->getPosition());
					myHero->setAction( GO_ACTION);
					myHero->setActionTarget( actionTmp);
				}
			}
		}
	}

	else
	{
		Ogre::Vector3 pointTmp = getCoordinatesMouse( x, y);

		if ( findNavMesh( pointTmp) != -1 )
		{
			myHero->clearPath();
			pathFinding( myHero, pointTmp);
			myHero->setAction( GO);
		}
	}
}

void AI::mouseRequest( float x, float y)
{
	Ogre::Real offsetXTmp = x / myWindow->getWidth();
	Ogre::Real offsetYTmp = y / myWindow->getHeight();

	Ogre::Ray testRayTmp = myHero->getCamera()->getCameraToViewportRay( offsetXTmp, offsetYTmp);
	myRaySceneQuery->setRay( testRayTmp);
	myRaySceneQuery->setQueryMask( ENEMY_FLAG);

	Ogre::RaySceneQueryResult& resultTmp = myRaySceneQuery->execute();

	CEGUI::Window* nameTmp = myGUI->findWindow( SHEET_BASE + SHEET_GAME + "Name");
	CEGUI::ProgressBar* lifeTmp = (CEGUI::ProgressBar*) myGUI->findWindow( SHEET_BASE + SHEET_GAME + "Life");

	if ( resultTmp.begin() != resultTmp.end() )
	{
		if ( resultTmp.begin()->movable )
		{
			Enemy* enemyTmp = myWorld->findEnemy( resultTmp.begin()->movable->getParentSceneNode()->getParentSceneNode()->getName());
			
			enemyTmp->setHighlight( true);
			myEnemyHighlight = enemyTmp;
			nameTmp->setText( enemyTmp->getFullName());
			lifeTmp->setVisible( true);
			lifeTmp->setProgress( (float) enemyTmp->getCurrentLife() / (float) enemyTmp->getLife());
		}

		else
		{
			if ( myEnemyHighlight )
			{
				myEnemyHighlight->setHighlight( false);
				myEnemyHighlight = NULL;
			}

			nameTmp->setText( "");
			lifeTmp->setVisible( false);
		}
	}

	else
	{
		if ( myEnemyHighlight )
		{
			myEnemyHighlight->setHighlight( false);
			myEnemyHighlight = NULL;
		}
		
		nameTmp->setText( "");
		lifeTmp->setVisible( false);
	}
}

Ogre::Vector3 AI::getCoordinatesMouse( float x, float y)
{
	Ogre::Real offsetXTmp = x / myWindow->getWidth();
	Ogre::Real offsetYTmp = y / myWindow->getHeight();

	Ogre::Ray testRayTmp = myHero->getCamera()->getCameraToViewportRay( offsetXTmp, offsetYTmp);

	std::pair<bool, Ogre::Real> resultTmp = testRayTmp.intersects( *myMap->getGround());

	if ( resultTmp.first )
	{
		return testRayTmp.getPoint( resultTmp.second);
	}

	return Ogre::Vector3::ZERO;
}

void AI::pathFinding( Personage* personage, const Ogre::Vector3& destination)
{	
	unsigned int origineTmp = findNavMesh( personage->getPosition());
	unsigned int destinationTmp = findNavMesh( destination);
	const Ogre::Vector2 destinationPosTmp( destination.x, destination.z);

	std::deque<Node*> listOpenTmp;
	std::deque<Node*> listCloseTmp;
	Node* startTmp;
	Node* currentTmp;
	Node* current2Tmp;
	int currentPosTmp;
	int deleteTmp;

	listOpenTmp.push_front( new Node());
	startTmp = listOpenTmp.front();

	startTmp->current = myNavMesh[origineTmp];
	startTmp->weightBefore = 0;
	startTmp->weightAfter = (destination.x - myNavMesh[origineTmp]->middle.x) * (destination.x - myNavMesh[origineTmp]->middle.x) + (destination.y - myNavMesh[origineTmp]->middle.y) * (destination.y - myNavMesh[origineTmp]->middle.y);
	startTmp->weightTotal = startTmp->weightBefore + startTmp->weightAfter;

	while ( true )
	{
		if ( !listOpenTmp.size() )
		{
			return;
		}

		currentTmp = listOpenTmp.front();
		deleteTmp = 0;

		for ( unsigned int i = 0; i < listOpenTmp.size(); i++ )
		{
			if ( listOpenTmp[i]->weightTotal < currentTmp->weightTotal )
			{
				currentTmp = listOpenTmp[i];
				deleteTmp = i;
			}
		}

		listCloseTmp.push_front( currentTmp);
		listOpenTmp.erase( listOpenTmp.begin() + deleteTmp);
		
		if ( currentTmp->current->number == destinationTmp )
		{
			break;
		}

		for ( unsigned int i = 0; i < currentTmp->current->neighbors.size(); i++ )
		{
			if ( find( listCloseTmp, currentTmp->current->neighbors[i]) == -1 )
			{ 
				if ( (currentPosTmp = find( listOpenTmp, currentTmp->current->neighbors[i])) != -1 )
				{
					if ( listOpenTmp[currentPosTmp]->weightBefore > currentTmp->weightBefore + myNavMesh[currentTmp->current->number]->distances[i] )
					{
						listOpenTmp[currentPosTmp]->previous = currentTmp;
						listOpenTmp[currentPosTmp]->previousNavMesh = currentTmp->current;
						listOpenTmp[currentPosTmp]->weightBefore = currentTmp->weightBefore + myNavMesh[currentTmp->current->number]->distances[i];
						listOpenTmp[currentPosTmp]->weightTotal = listOpenTmp[currentPosTmp]->weightBefore + listOpenTmp[currentPosTmp]->weightAfter;
					}
				}

				else
				{
					listOpenTmp.push_front( new Node());

					listOpenTmp[0]->current = myNavMesh[currentTmp->current->number]->neighbors[i];
					listOpenTmp[0]->previous = currentTmp;
					listOpenTmp[0]->previousNavMesh = currentTmp->current;
					listOpenTmp[0]->weightBefore = currentTmp->weightBefore + myNavMesh[currentTmp->current->number]->distances[i];
					listOpenTmp[0]->weightAfter = (destination.x - myNavMesh[listOpenTmp[0]->current->number]->middle.x) * (destination.x - myNavMesh[listOpenTmp[0]->current->number]->middle.x) + (destination.y - myNavMesh[listOpenTmp[0]->current->number]->middle.y) * (destination.y - myNavMesh[listOpenTmp[0]->current->number]->middle.y);
					listOpenTmp[0]->weightTotal = listOpenTmp[0]->weightBefore + listOpenTmp[0]->weightAfter;
				}
			}
		}
	}

	current2Tmp = currentTmp;
	
	while ( true )
	{
		bool normal = true;

		if ( currentTmp->current->number == destinationTmp && currentTmp->current->number != origineTmp )
		{
			normal = false;

			Ogre::Vector2 startTmp = findMidPoint( currentTmp->current, currentTmp->previousNavMesh);
			Ogre::Vector3 directionTmp( destinationPosTmp.x - startTmp.x, 0, destinationPosTmp.y - startTmp.y);
			personage->addDirection( directionTmp.normalisedCopy(), directionTmp.length());

			currentTmp = currentTmp->previous;
		}

		if ( currentTmp->current->number == origineTmp && currentTmp->current->number != destinationTmp )
		{
			normal = false;

			Ogre::Vector2 endTmp = findMidPoint( current2Tmp->current, current2Tmp->previousNavMesh);
			Ogre::Vector2 startTmp( personage->getPosition().x, personage->getPosition().z);
			Ogre::Vector3 directionTmp( endTmp.x - startTmp.x, 0, endTmp.y - startTmp.y);	
			personage->addDirection( directionTmp.normalisedCopy(), directionTmp.length());

			break;
		}

		if ( currentTmp->current->number == origineTmp && currentTmp->current->number == destinationTmp )
		{
			normal = false;
			
			Ogre::Vector2 startTmp( personage->getPosition().x, personage->getPosition().z);
			Ogre::Vector3 directionTmp( destinationPosTmp.x - startTmp.x, 0, destinationPosTmp.y - startTmp.y);
			personage->addDirection( directionTmp.normalisedCopy(), directionTmp.length());

			break;
		}

		if ( normal )
		{
			Ogre::Vector2 endTmp = findMidPoint( current2Tmp->current, current2Tmp->previousNavMesh);
			Ogre::Vector2 startTmp = findMidPoint( currentTmp->current, currentTmp->previousNavMesh);
			Ogre::Vector3 directionTmp( endTmp.x - startTmp.x, 0, endTmp.y - startTmp.y);	
			personage->addDirection( directionTmp.normalisedCopy(), directionTmp.length());

			current2Tmp = currentTmp;
			currentTmp = currentTmp->previous;
		}
	}
}

int AI::findNavMesh( const Ogre::Vector3& position)
{
	for ( int i = 0; i < myNavMesh.size(); i++ )
	{
		if ( isInsidePolygon(  myNavMesh[i], Ogre::Vector2( position.x, position.z)) )
		{
			return i;
		}
	}

	return -1;
}

bool AI::isInsidePolygon( NavMeshPol* polygon, const Ogre::Vector2& position)
{
	for ( unsigned int i = 1; i < polygon->points.size(); i++ )
	{
		Ogre::Vector2& firstPoint = polygon->points[i-1];
		Ogre::Vector2& secondPoint = polygon->points[i];
		Ogre::Vector2& middle = polygon->middle;

		float delta = (secondPoint.y - firstPoint.y) / (secondPoint.x - firstPoint.x);

		if ( middle.y > delta * ( middle.x - firstPoint.x ) + firstPoint.y )
		{
			if ( position.y >= delta * ( position.x - firstPoint.x ) + firstPoint.y );

			else
			{
				return false;
			}
		}

		else
		{
			if ( position.y <= delta * ( position.x - firstPoint.x ) + firstPoint.y );

			else
			{
				return false;
			}
		}
	}

	Ogre::Vector2& firstPoint = polygon->points[polygon->points.size() - 1];
	Ogre::Vector2& secondPoint = polygon->points[0];
	Ogre::Vector2& middle = polygon->middle;

	float delta = (secondPoint.y - firstPoint.y) / (secondPoint.x - firstPoint.x);

	if ( middle.y > delta * ( middle.x - firstPoint.x ) + firstPoint.y )
	{
		if ( position.y > delta * ( position.x - firstPoint.x ) + firstPoint.y );

		else
		{
			return false;
		}
	}

	else
	{
		if ( position.y < delta * ( position.x - firstPoint.x ) + firstPoint.y );

		else
		{
			return false;
		}
	}

	return true;
}

int AI::find( const std::deque<Node*>& list, const NavMeshPol* navMesh)
{
	for ( unsigned i = 0; i < list.size(); i++ )
	{
		if ( list.at( i)->current == navMesh )
		{
			return i;
		}
	}

	return -1;
}

const Ogre::Vector2& AI::findMidPoint( const NavMeshPol* first, const NavMeshPol* second)
{
	for ( unsigned int i = 0; i < second->neighbors.size(); i++ )
	{
		if ( first == second->neighbors.at( i) )
		{
			return second->neighborMidPoint.at( i);
		}
	}
}